//
//  main.cpp
//  aes_ecb_cryptor
//
//  Created by tanwz on 16/5/23.
//  Copyright © 2016年 tanwz. All rights reserved.
//

#include <iostream>
#include <CommonCrypto/CommonCrypto.h>


bool parserKey(const char *keyStr, unsigned char key[])
{
    if (strlen(keyStr) != 34) {
        printf("key invalid (length != 34)\n");
        return false;
    }
    
    char prefix[3] = {0};
    strncpy(prefix, keyStr, 2);
    if (strcmp(prefix, "0x") !=0 && strcmp(prefix, "0X") != 0) {
        printf("key invalid (should start with 0x)\n");
        return false;
    }
    
    char byte[3] = {0};
    for (int i = 2; i < 34; i += 2) {
        strncpy(byte, keyStr + i, 2);
        unsigned long ret = strtoul(byte, NULL, 16);
        if (ret == ERANGE) {
            printf("invalid key");
            return false;
        }
        key[(i - 2) / 2] = (unsigned char)ret;
    }
    return true;
}

bool validateArgs(int argc, const char * argv[],
                  CCOperation *operation,
                  unsigned char key[],
                  char *inPath,
                  char *outPath
                  )
{
    // aes_ecb_cryptor en -key(0x) inpath outpath
    if (argc != 6) {
        printf("invalide args, using -e/d -key key inpath outpath\n");
        return false;
    }
    
    //operation
    if (strcmp(argv[1], "-e") == 0) {
        *operation = kCCEncrypt;
    }
    else if (strcmp(argv[1], "-d") == 0) {
        *operation = kCCDecrypt;
    }
    else {
        printf("invalid operation argv[1], using -e(encrypt) or -d(decrypt)\n");
        return false;
    }
    
    //key
    if (strcmp(argv[2], "-key") != 0) {
        printf("invalid args argv[2]\n");
        return false;
    }
    
    if (!parserKey(argv[3], key)) {
        return false;
    }
    
    //inpath
    if (strlen(argv[4]) > 1024) {
        printf("invalid inpath argv[4]\n");
        return false;
    }
    strcpy(inPath, argv[4]);
    
    //outpath
    if (strlen(argv[5]) > 1024) {
        printf("invalid oupath argv[4]\n");
        return false;
    }
    strcpy(outPath, argv[5]);
    
    return true;
}

int main(int argc, const char * argv[]) {
    
    CCOperation operation;
    unsigned char key[16] = {0};
    char inpath[1024] = {0};
    char outpath[1024] = {0};
    
    if(!validateArgs(argc, argv, &operation, key, inpath, outpath)) {
        printf("failed!\n");
        return 0;
    }
    
    FILE *inFile = fopen(inpath, "rb");
    FILE *outFile = fopen(outpath, "wb+");
    
    fpos_t fileSize = 0;
    fseek(inFile, 0, SEEK_END);
    fgetpos(inFile, &fileSize);
    fseek(inFile, 0, SEEK_SET);
    
    const int readBufSize = 4096;
    const int encryptBufSize = readBufSize + 16;
    unsigned char readBuf[readBufSize] = {0};
    unsigned char encrpytBuf[encryptBufSize] = {0};
    size_t encrpyptBufLen = readBufSize;
    size_t readLen = 0;
    long offset = 0;
    bool reachEnd = false;
    bool success = true;
    
    CCCryptorRef ref = NULL;
    CCCryptorStatus status = CCCryptorCreate(operation, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode, key, 16, NULL, &ref);
    if (operation == kCCEncrypt) {
        printf("encrypt...\n");
    } else {
        printf("decrypt...\n");
    }
    
    do {
        readLen = (int)fread((void *)readBuf, (size_t)1, (size_t)readBufSize, inFile);
        
        if (readLen == 0) {
            break;
        }
        
        if (kCCSuccess != CCCryptorUpdate(ref, readBuf, readLen, encrpytBuf, encryptBufSize, &encrpyptBufLen)) {
            success = false;
            break;
        }
        
        if (encrpyptBufLen != fwrite(encrpytBuf, 1, encrpyptBufLen, outFile)) {
            success = false;
            break;
        }
        
        offset += encrpyptBufLen;
        
        printf("\r%02d%%", (int)(offset * 100 / fileSize));
        
    } while (readLen > 0 && !reachEnd);
    
    if (success) {
        status = CCCryptorFinal(ref, encrpytBuf, encrpyptBufLen, &encrpyptBufLen);
        if (status == kCCSuccess && encrpyptBufLen > 0) {
            fwrite(encrpytBuf, 1, encrpyptBufLen, outFile);
        }
    }
    
    fclose(inFile);
    fclose(outFile);
    
    if (success) {
        printf("\nsuccessed\n");
    } else {
        printf("\nfailed\n");
    }
    
    return 0;
}
